/*
 ** server.c -- a stream socket server demo
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <assert.h>
#include <cstdlib>
using namespace std;

#define PORT "3490"  // the port users will be connecting to

#define BACKLOG 10	 // how many pending connections queue will hold

string get_chid(const char*);

/* send command to server 
   - server parses the req.
   - if ("spawn #chid") , check if exists else fork a new channel
   - keep a list of channel ids 
   */


void sigchld_handler(int s)
{
  while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
  if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }

  return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
  int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_storage their_addr; // connector's address information
  socklen_t sin_size;
  struct sigaction sa;
  int yes=1;
  char s[INET6_ADDRSTRLEN];
  int rv;
  char buf[100];

  vector<std::string> listChannels(0);

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE; // use my IP

  if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    return 1;
  }

  // loop through all the results and bind to the first we can
  for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
      perror("server: socket");
      continue;
    }

    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
          sizeof(int)) == -1) {
      perror("setsockopt");
      exit(1);
    }

    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      perror("server: bind");
      continue;
    }

    break;
  }

  if (p == NULL)  {
    fprintf(stderr, "server: failed to bind\n");
    return 2;
  }

  freeaddrinfo(servinfo); // all done with this structure

  if (listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
  }

  sa.sa_handler = sigchld_handler; // reap all dead processes
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;
  if (sigaction(SIGCHLD, &sa, NULL) == -1) {
    perror("sigaction");
    exit(1);
  }

  printf("server: waiting for connections...\n");
  int numbytes, junk;
  string clientComm;

  /*
    when server receives >1 request of type "spwan 123", then only it spawns
    chat window for "123"
  */
  while(1) {
    sin_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
    if (new_fd == -1) {
      perror("accept");
      continue;
    }

    inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
    printf("server: got connection from %s\n", s);

    if ((numbytes = recv(new_fd, buf, 100-1, 0)) == -1) {
      perror("recv");
      exit(1);
    } // supposedly = "spawn chid12"

    buf[numbytes] = '\0';
    std::cout << "\n\t\t $$$ recv from client $$$ = [" << buf << "]"; 

    cout << "\n channel id = [" << get_chid(buf) << "]";

    cout << "\n hit enter ";  cin.get();

    int child = fork();
    if (!child) {
      // child 
      close(sockfd); // child doesn't need the listener

      // sleep for 10 seconds and then send "test10" to client
      cout << "\n \t\tThis is child procesing 1 client | sleeping10\n";
      sleep (5);

      string test = "test10";
      if (send(new_fd, test.c_str(), (test.length()), 0) == -1)
        //if (send(new_fd, "Hello, world!", 13, 0) == -1)
        perror("send");
      close(new_fd);
      sleep (20);

      cout << "\n \t\tChild - Exit\n";
      exit(0);
    }
    else {
      // parent
      close(new_fd);  // parent doesn't need this
    }
  } // while

  return 0;
}

string get_chid (const char *buf) {
  string t = string(buf);
  size_t found = t.find(' ');
  if (found == string::npos)
    assert(0);
  else {
    string tmp = t.substr(found+1);
    //cout << "\ntmp = " << tmp;
    return tmp;
  }
}
